#include <assert.h>
#include <errno.h>
#include <inttypes.h>
#include <limits.h>
#include <signal.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <unistd.h>

#ifdef USE_CAPSICUM
#include <sys/capsicum.h>
#endif

#include <err.h>
#include <sysexits.h>

#include "au.h"

#ifndef MIN
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#endif
static int	au_plot(struct au *, void *, size_t, FILE *);
static uint32_t	get4(void *);
static ssize_t	nread(int, void *, size_t);
static ssize_t	nwrite(int, const void *, size_t);

int
main(int argc, char *argv[])
{
	char hb[24];
#ifdef USE_CAPSICUM
	cap_rights_t rights;
#endif
	struct au au;
	size_t refresh = 50;
	size_t bsize = 0;
	void *b = NULL;
	const char *plot = "plot";
	char *p;
	FILE *pfp;
	unsigned long ul;
	ssize_t r;
	size_t i;
	size_t t;
	int c;

	while ((c = getopt(argc, argv, "c:r:")) != -1)
		switch (c) {
		case 'c':
			plot = optarg;
			break;
		case 'r':
			errno = 0;
			ul = strtoul(optarg, &p, 0);
			if (ul > UINT32_MAX)
				errno = ERANGE;
			if (p == optarg || *p != '\0' || errno != 0)
				err(EX_USAGE, "Bad number %s", optarg);
			refresh = ul;
			break;
		default:
			goto usage;
		}
	if ((pfp = popen(plot, "w")) == NULL)
		err(EX_USAGE, "Could not execute '%s'", plot);
#ifdef USE_PLEDGE
	pledge("stdio", NULL);
#endif
#ifdef USE_CAPSICUM
	if (cap_enter() == -1 && errno != ENOSYS)
		err(EX_OSERR, "Could not enter capibility mode");
	cap_rights_init(&rights, CAP_READ);
	if (cap_rights_limit(STDIN_FILENO, &rights) == -1 &&
	    errno != ENOSYS)
		err(EX_OSERR, "Could not limit the input");
	cap_rights_init(&rights, CAP_WRITE);
	if (cap_rights_limit(STDOUT_FILENO, &rights) == -1 &&
	    errno != ENOSYS)
		err(EX_OSERR, "Could not limit the output");
	if (cap_rights_limit(STDERR_FILENO, &rights) == -1 &&
	    errno != ENOSYS)
		err(EX_OSERR, "Could not limit the standard error");
	if (cap_rights_limit(fileno(pfp), &rights) == -1 &&
	    errno != ENOSYS)
		err(EX_OSERR, "Could not limit the plot command");
#endif
	while (nread(STDIN_FILENO, hb, 24) > 0) {
		if (strncmp(hb, ".snd", 4) != 0)
			err(EX_DATAERR, "Invalid magic number "
			    "%02X %02X %02X %02X",
			    ((uint8_t *)hb)[0],
			    ((uint8_t *)hb)[1],
			    ((uint8_t *)hb)[2],
			    ((uint8_t *)hb)[3]);
		au.au_off = get4((char *)hb + 4);
		au.au_size = get4((char *)hb + 8);
		au.au_enc = get4((char *)hb + 12);
		au.au_rate = get4((char *)hb + 16);
		au.au_chan = get4((char *)hb + 20);
		t = (au_ssize(&au) * au.au_rate + (refresh - 1)) / refresh;
		if (bsize < t && (b = b == NULL ? malloc(t) :
		    realloc(b, (bsize = t))) == NULL)
			err(EX_OSERR,
			    "Could not allocate the buffer");
		if (nwrite(STDOUT_FILENO, hb, 24) < 0)
			err(EX_IOERR,
			    "Could not write audio header");
		while ((r = nread(STDIN_FILENO, b,
		    MIN(au.au_size, t))) > 0) {
			if (nwrite(STDOUT_FILENO, b, r) != r)
				err(EX_IOERR, "Write error");
			if (au_plot(&au, b, r, pfp) != 0)
				goto ret;
			if (au.au_size != UINT32_MAX)
				au.au_size -= r;
		}
		if (r < 0)
			err(EX_IOERR, "Error reading from the "
			    "standard input");
	}
ret:
	free(b);
	return (EX_OK);
usage:
	(void)fprintf(stderr,
	    "usage: auplot [-c command] [-r refresh-rate]\n");
	return (EX_USAGE);
}

int
au_plot(struct au *au, void *b, size_t s, FILE *fp)
{
	uint8_t *up;
	size_t as;
	size_t av;
	size_t i;
	size_t k;
	size_t c;
	size_t l;
	uint32_t pcm;

	assert(au != NULL && b != NULL && fp != NULL);
	if (fprintf(fp, "e\n") < 0)
		return (-1);
	up = b;
	av = au_vsize(au);
	as = au_ssize(au);
	l = s / as;
	switch (au->au_enc) {
	case AU_PCM8:
		/* FALLTHROUGH */
	case AU_PCM16:
		/* FALLTHROUGH */
	case AU_PCM24:
		/* FALLTHROUGH */
	case AU_PCM32:
		if (fprintf(fp, "ra 0 0 %zu %"PRIu32"\n", l, UINT32_MAX) < 0)
			return (-1);
		for (c = 0; c < au->au_chan; c++)
			for (i = 0; i < l; i++) {
				up = (uint8_t *)b +
				    (c + i * au->au_chan) * av;
				for (pcm = 0, k = 0; k < 4; k++)
					pcm = (pcm << 8) | up[k % av];
				if (fprintf(fp, "%c %zu %"PRIu32"\n",
				    i == 0 ? 'm' : 'v', i,
				    pcm ^ UINT32_C(0x80000000)) < 0)
					return (-1);
			}
		break;
	case AU_FLOAT32:
		if (fprintf(fp, "ra 0 -1 %zu 1\n", l) < 0)
			return (-1);
		au_nswap(au, b, s);
		for (c = 0; c < au->au_chan; c++)
			for (i = 0; i < l; i++)
				if (fprintf(fp, "%c %zu %f\n",
				    i == 0 ? 'm' : 'v', i,
				    ((float *)b)[c + i * au->au_chan])
				    < 0)
					return (-1);
		break;
	case AU_FLOAT64:
		if (fprintf(fp, "ra 0 -1 %zu 1\n", l) < 0)
			return (-1);
		au_nswap(au, b, s);
		for (c = 0; c < au->au_chan; c++)
			for (i = 0; i < l; i++)
				if (fprintf(fp, "%c %zu %f\n",
				    i == 0 ? 'm' : 'v', i,
				    ((double *)b)[c + i * au->au_chan])
				    < 0)
					return (-1);
		break;
	default:
		err(EX_DATAERR, "Invalid encoding %s",
		    au_encstr(au->au_enc));
	}
	return (fprintf(fp, "cl\n") < 0 ? -1 : 0);
}

uint32_t
get4(void *p)
{
	uint8_t *u = p;

	return (((uint32_t)u[0] << 24) | ((uint32_t)u[1] << 16) |
	    ((uint32_t)u[2] << 8) | (uint32_t)u[3]);
}

ssize_t
nread(int fd, void *b, size_t s)
{
	size_t i;
	ssize_t t;

	if (b == NULL) {
		errno = EINVAL;
		return (-1);
	}
	if (s > SSIZE_MAX) {
		errno = ERANGE;
		return (-1);
	}
	for (i = 0; i < s; i += t)
		if ((t = read(fd, (char *)b + i, s - i)) <= 0)
			return (t);
	return (s);
}

ssize_t
nwrite(int fd, const void *b, size_t s)
{
	size_t i;
	ssize_t t;

	if (b == NULL) {
		errno = EINVAL;
		return (-1);
	}
	if (s > SSIZE_MAX) {
		errno = ERANGE;
		return (-1);
	}
	for (i = 0; i < s; i += t)
		if ((t = write(fd, (const char *)b + i, s - i)) <= 0)
			return (t);
	return (s);
}
